/**
 * The contents of this file are subject to the license and copyright detailed in the LICENSE and NOTICE files at the root of the source tree and available online at
 * http://www.askosi.org/license.html
 */
package be.destin.skos.core;

import java.util.LinkedList;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;

import org.apache.log4j.Logger;

/**
 * @author Christophe Dupriez
 *
 */
@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlType(propOrder={"propertyClass","system","mandatory","repeatable","minOccurs","maxOccurs","normalized","minLength","maxLength","closed","maxPrefix","maxSuffix","languageCollection","language","prefix","suffix",/*"subfield",*/"defaultScheme","defaultPrefix","defaultSuffix","conceptScheme","notationScheme","broaderScheme","external"})
public class MetadataProperty extends Concept {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/** log4j category */
	private static Logger log = Logger.getLogger(MetadataProperty.class);

	private MetadataPropertyClass propertyClass = MetadataPropertyClass.TEXT; // type of data stored
	private boolean system = false; // field is provided by system and cannot be manually modified?
	private boolean mandatory = false; // field must be non empty?
	private boolean repeatable = true; // field can be repeated?
	private boolean normalized = false; // field should be normalized: notations should be translated to about, labels should be replaced by default_scheme about,etc.
	private int minLength = 1; // minimal length of a non-empty field
	private int maxLength = 999999; // maximal length
	private LinkedList<String> prefix; // from the prefix list
	private LinkedList<String> suffix; // from the suffix list
	//private LinkedList<String> subfield; // from the subfield list
	private String languageCollection; // Collection of allowed languages
	private LinkedList<String> language; // from the language scheme
	private String defaultScheme; // Main list for which no list code is necessary
	private String defaultPrefix = null;
	private String defaultSuffix = null;
	private LinkedList<String> conceptScheme; // Controlling conceptScheme
	private LinkedList<String> notationScheme; // Controlling conceptScheme (notationScheme: for search only)
	private LinkedList<String> broaderScheme; // When looking for broadMatch, other schemes involved (broadMatch indexing)
	private LinkedList<String> external; // from the external sources list
	private boolean closed = false; // values outside conceptScheme allowed?
	private int maxPrefix = -1; // maximum number of prefixes allowed?
	private int maxSuffix = -1; // maximum number of suffixes allowed?
	private int minOccurs = 0;
	private int maxOccurs = 999999;
	
    private NoScheme effectiveDefaultScheme;
	private LinkedList<ConceptScheme> effectiveConceptScheme; // Controlling conceptScheme
	private LinkedList<NotationScheme> effectiveNotationScheme; // Controlling conceptScheme (notationScheme: for search only)

	public MetadataProperty() {
    	super();
    }

	public MetadataProperty(ConceptScheme list) {
    	super(list);
    }

	/**
	 * @return the main
	 */
	public String getDefaultScheme() {
		return defaultScheme;
	}
	/**
	 * @return the notationScheme
	 */
	public LinkedList<String> getNotationScheme() {
		if (this.notationScheme==null) this.notationScheme = new LinkedList<String>();
		return this.notationScheme;
	}
	/**
	 * @return the external
	 */
	public LinkedList<String> getExternal() {
		if (this.external==null) this.external = new LinkedList<String>();
		return this.external;
	}
	/**
	 * @return the propertyClass
	 */
	@XmlAttribute (required=true)
	public MetadataPropertyClass getPropertyClass() {
		return propertyClass;
	}
	/**
	 * @return the maxLength
	 */
	@XmlAttribute
	public int getMaxLength() {
		return maxLength;
	}
	/**
	 * @return the minLength
	 */
	@XmlAttribute
	public int getMinLength() {
		return minLength;
	}
	/**
	 * @return the prefix
	 */
	public LinkedList<String> getPrefix() {
		if (this.prefix==null) this.prefix = new LinkedList<String>();
		return this.prefix;
	}
	/**
	 * @return the notationScheme
	 */
	public LinkedList<String> getConceptScheme() {
		if (this.conceptScheme==null) this.conceptScheme = new LinkedList<String>();
		return this.conceptScheme;
	}
	/**
	 * @return the suffix
	 */
	public LinkedList<String> getSuffix() {
		if (this.suffix==null) this.suffix = new LinkedList<String>();
		return suffix;
	}
	/**
	public LinkedList<String> getSubfield() {
		if (this.subfield==null) this.subfield = new LinkedList<String>();
		return subfield;
	}
	 */
	/**
	 * @return the closed
	 */
	@XmlAttribute
	public boolean isClosed() {
		return closed;
	}
	/**
	 * @return the mandatory
	 */
	@XmlAttribute
	public boolean isMandatory() {
		return mandatory;
	}
	/**
	 * @return the repeatable
	 */
	@XmlAttribute
	public boolean isRepeatable() {
		return repeatable;
	}
	/**
	 * @param closed the closed to set
	 */
	public void setClosed(boolean closed) {
		this.closed = closed;
	}
	/**
	 * @param main the main to set
	 */
	public void setDefaultScheme(String main) {
		if (main == null) this.defaultScheme = null;
		else this.defaultScheme = SkosUtil.normalizeAbout(main);
	}

	/**
	 * @param notationScheme the notationScheme to set
	 */
	public void setNotationScheme(LinkedList<String> derivedes) {
		this.notationScheme = SkosUtil.cleanList(derivedes);
	}
	/**
	 * @param external the external to set
	 */
	public void setExternal(LinkedList<String> externales) {
		this.external = externales;
	}

	/**
	 * @param propertyClass the propertyClass to set
	 */
	public void setPropertyClass(MetadataPropertyClass metadataPropertyClass) {
		this.propertyClass = metadataPropertyClass;
	}
	/**
	 * @param mandatory the mandatory to set
	 */
	public void setMandatory(boolean mandatory) {
		this.mandatory = mandatory;
	}
	

	/**
	 * @param maxLength the maxLength to set
	 */
	public void setMaxLength(int maxLength) {
		this.maxLength = maxLength;
	}
	/**
	 * @param minLength the minLength to set
	 */
	public void setMinLength(int minLength) {
		this.minLength = minLength;
	}

	/**
	 * @param prefix the prefix to set
	 */
	public void setPrefix(LinkedList<String> prefixes) {
		this.prefix = SkosUtil.cleanList(prefixes);
	}
	/**
	 * @param repeatable the repeatable to set
	 */
	public void setRepeatable(boolean repeatable) {
		this.repeatable = repeatable;
	}
	/**
	 * @param notationScheme the notationScheme to set
	 */
	public void setConceptScheme(LinkedList<String> authoritiesCodes) {
		this.conceptScheme = SkosUtil.cleanList(authoritiesCodes);
	}
	/**
	 * @param suffix the suffix to set
	 */
	public void setSuffix(LinkedList<String> suffixes) {
		this.suffix = SkosUtil.cleanList(suffixes);
	}

	/**
	 * @param subfield the subfield to set
	public void setSubfield(LinkedList<String> subfields) {
		this.subfield = SkosUtil.cleanList(subfields);
	}
	 */

	/**
	 * @return the language
	 */
	public LinkedList<String> getLanguage() {
		return language;
	}

	/**
	 * @param language the language to set
	 */
	public void setLanguage(LinkedList<String> language) {
		this.language = SkosUtil.cleanList(language);
	}

	/**
	 * @param languageCollection the language collection to set
	 */
	public void setLanguageCollection(String languageCollection) {
		if (languageCollection == null) this.languageCollection = null;
		else this.languageCollection = SkosUtil.normalizeAbout(languageCollection);
	}

	/**
	 * @return the collection of allowed language
	 */
	public String getLanguageCollection() {
		return languageCollection;
	}

	/**
	 * @return the collection of allowed language
	 */
	@XmlTransient
	public LinkedList<String> getAllowedLanguage() {
		LinkedList<String> allowedLanguage = new LinkedList<String>();
		if (languageCollection != null && languageCollection.length() > 0) {
			if (inScheme == null)
				log.error("Languages of "+this.getAbout()+", null scheme for this property!");
			else {
				SkosManager skosManager = this.getManagerInstance();
				if (skosManager == null) {
					log.error(this.getAbout()+" not linked to a SkosManager instance?");
					return null;
				}
				SchemeInterface aCollection = skosManager.getScheme(languageCollection);
				if (aCollection == null) {
					aCollection = skosManager.openScheme(languageCollection);
				}
				if (aCollection != null) {
					for (Concept aConcept : aCollection.getAllConcepts()) {
						allowedLanguage.add(aConcept.getAbout());
					}
				}
			}
		}
	
		if (language != null && language.size() > 0)
			allowedLanguage.addAll(language);
		if (allowedLanguage.size() == 0) return null;
		return allowedLanguage;
	}

	/**
	 * @return the defaultPrefix
	 */
	public String getDefaultPrefix() {
		return defaultPrefix;
	}

	/**
	 * @param defaultPrefix the defaultPrefix to set
	 */
	public void setDefaultPrefix(String defaultPrefix) {
		if (defaultPrefix == null) this.defaultPrefix = null;
		else this.defaultPrefix = SkosUtil.normalizeAbout(defaultPrefix);
	}

	/**
	 * @return the defaultSuffix
	 */
	public String getDefaultSuffix() {
		return defaultSuffix;
	}

	/**
	 * @param defaultSuffix the defaultSuffix to set
	 */
	public void setDefaultSuffix(String defaultSuffix) {
		if (defaultSuffix == null) this.defaultSuffix = null;
		else this.defaultSuffix = SkosUtil.normalizeAbout(defaultSuffix);
	}

	/**
	 * @return the system
	 */
	@XmlAttribute
	public boolean isSystem() {
		return system;
	}

	/**
	 * @param system the system to set
	 */
	public void setSystem(boolean system) {
		this.system = system;
	}

	/**
	 * @return the normalized
	 */
	@XmlAttribute
	public boolean isNormalized() {
		return normalized;
	}

	/**
	 * @param normalized the normalized to set
	 */
	public void setNormalized(boolean normalized) {
		this.normalized = normalized;
	}

	/**
	 * @return the broaderScheme
	 */
	public LinkedList<String> getBroaderScheme() {
		if (this.broaderScheme==null) this.broaderScheme = new LinkedList<String>();
		return broaderScheme;
	}

	/**
	 * @param broaderScheme the broaderScheme to set
	 */
	public void setBroaderScheme(LinkedList<String> broaderScheme) {
		this.broaderScheme = SkosUtil.cleanList(broaderScheme);
	}

	/**
	 * @return the maxPrefix
	 */
	@XmlAttribute
	public int getMaxPrefix() {
		if (prefix == null) return 0;
		if (maxPrefix < 0) return 1; // default value depends on existing prefixes
		return maxPrefix;
	}

	/**
	 * @param maxPrefix the maxPrefix to set
	 */
	public void setMaxPrefix(int maxPrefix) {
		this.maxPrefix = maxPrefix;
	}

	/**
	 * @return the maxSuffix
	 */
	@XmlAttribute
	public int getMaxSuffix() {
		if (suffix == null) return 0;
		if (maxSuffix < 0) return 1; // default value depends on existing sufffixes
		return maxSuffix;
	}

	/**
	 * @param maxSuffix the maxSuffix to set
	 */
	public void setMaxSuffix(int maxSuffix) {
		this.maxSuffix = maxSuffix;
	}

	@XmlAttribute
	public int getMinOccurs() {
		if (!mandatory) return 0;
		return (minOccurs > 1 ? minOccurs : 1);
	}

	public void setMinOccurs(int minOccurs) {
		this.minOccurs = minOccurs;
	}

	@XmlAttribute
	public int getMaxOccurs() {
		if (!repeatable) {
			return (maxOccurs >= 1 ? 1 : 0);
		} else {
			int min = getMinOccurs();
			if (maxOccurs <= min) return min;
			return maxOccurs;
		}
	}

	public void setMaxOccurs(int maxOccurs) {
		this.maxOccurs = maxOccurs;
	}
	
	public SchemeInterface[] getSchemes() {
		LinkedList<SchemeInterface> resultList = new LinkedList<SchemeInterface>();
		for (String schemeCode : this.getConceptScheme() ) {
			SchemeInterface scheme = this.getManagerInstance().getScheme(schemeCode);
			if (scheme != null && scheme.getClass() != NoScheme.class) {
				resultList.add(scheme);
			}
		}
		for (String schemeCode : this.getNotationScheme() ) {
			SchemeInterface scheme = this.getManagerInstance().getScheme(schemeCode);
			if (scheme != null && scheme.getClass() != NoScheme.class) {
				resultList.add(scheme);
			}
		}
		return resultList.toArray(new SchemeInterface[resultList.size()]);
	}

	/**
	 * @return the effectiveDefaultScheme
	 */
	@XmlTransient
	public NoScheme getEffectiveDefaultScheme() {
		if (effectiveDefaultScheme == null) {
			if (defaultScheme == null) return null;
			SchemeInterface aScheme = this.getInScheme().getManagerInstance().openScheme(defaultScheme);
			if (aScheme instanceof NoScheme) {
				effectiveDefaultScheme = (NoScheme) aScheme;
			}
		}
		return effectiveDefaultScheme;
	}

	/**
	 * @param effectiveDefaultScheme the effectiveDefaultScheme to set
	 */
	public void setEffectiveDefaultScheme(NoScheme effectiveDefaultScheme) {
		this.effectiveDefaultScheme = effectiveDefaultScheme;
	}

	/**
	 * @return the effectiveConceptScheme
	 */
	@XmlTransient
	public LinkedList<ConceptScheme> getEffectiveConceptScheme() {
		if (effectiveConceptScheme == null) {
			if (conceptScheme == null || conceptScheme.size()==0) return null;
			effectiveConceptScheme = new LinkedList<ConceptScheme>();
			for (String aSchemeAbout : conceptScheme) {
				SchemeInterface aScheme = this.getInScheme().getManagerInstance().openScheme(aSchemeAbout);
				if (aScheme != null && aScheme instanceof ConceptScheme) {
					effectiveConceptScheme.add((ConceptScheme)aScheme);
				}
			}
		}
		return effectiveConceptScheme;
	}

	/**
	 * @param effectiveConceptScheme the effectiveConceptScheme to set
	 */
	public void setEffectiveConceptScheme(
			LinkedList<ConceptScheme> effectiveConceptScheme) {
		this.effectiveConceptScheme = effectiveConceptScheme;
	}

	/**
	 * @return the effectiveNotationScheme
	 */
	@XmlTransient
	public LinkedList<NotationScheme> getEffectiveNotationScheme() {
		if (effectiveNotationScheme == null) {
			if (notationScheme == null || notationScheme.size()==0) return null;
			effectiveNotationScheme = new LinkedList<NotationScheme>();
			for (String aSchemeAbout : notationScheme) {
				SchemeInterface aScheme = this.getInScheme().getManagerInstance().openScheme(aSchemeAbout);
				if (aScheme != null && aScheme instanceof NotationScheme) {
					effectiveNotationScheme.add((NotationScheme)aScheme);
				}
			}
		}
		return effectiveNotationScheme;
	}

	/**
	 * @param effectiveNotationScheme the effectiveNotationScheme to set
	 */
	public void setEffectiveNotationScheme(
			LinkedList<NotationScheme> effectiveNotationScheme) {
		this.effectiveNotationScheme = effectiveNotationScheme;
	}

	public boolean equals(Object o) {
		return super.equals(o);
	}

}
